#pragma once

#include <cstdio>

enum Axis {
    A_X,
    A_Y,
    A_Z
};

enum ColorIndex : unsigned char{
    red,
    orange,
    blue,
    green,
    white,
    yellow,
    black
};

enum Steps : unsigned char {
    ST_NONE = 0,
    ST_L, ST_R, ST_U, ST_D, ST_F, ST_B,
    ST_Lb, ST_Rb, ST_Ub, ST_Db, ST_Fb, ST_Bb,
    ST_l, ST_r, ST_u, ST_d, ST_f, ST_b,
    ST_lb, ST_rb, ST_ub, ST_db, ST_fb, ST_bb,
    ST_END,
};


struct ColorSet{
    ColorIndex up;      //0
    ColorIndex down;    //1
    ColorIndex left;    //2
    ColorIndex right;   //3
    ColorIndex front;   //4
    ColorIndex back;    //5
};

#define cidx(field) (unsigned char)(unsigned long)(&(((ColorSet*)0)->field))

union ColorMap {
    ColorSet set;
    ColorIndex items[6];
};

struct Item{
    ColorMap colors{};

    Item() = default;

    explicit Item(ColorMap &map) {
        this->colors = map;
    }
};

struct LayerStepMeta {
    Steps step;
    Axis axis;
    int angle;
    unsigned char layers;
    unsigned char color_index;
    unsigned char count;
    LayerStepMeta(Steps step, Axis axis, int angle, unsigned char layers, unsigned char color_index, unsigned char count) {
        this->step = step;
        this->axis = axis;
        this->angle = angle;
        this->layers = layers;
        this->color_index = color_index;
        this->count = count;
    }
};


LayerStepMeta step_meta[] = {
        [Steps::ST_NONE] = LayerStepMeta(Steps::ST_NONE, Axis::A_X, 0, 0b001, 0, 0),

        [Steps::ST_L] = LayerStepMeta(Steps::ST_L, Axis::A_X, 90, 0b001, cidx(left), 3),
        [Steps::ST_R] = LayerStepMeta(Steps::ST_R, Axis::A_X, -90, 0b100, cidx(right), 1),

        [Steps::ST_U] = LayerStepMeta(Steps::ST_U, Axis::A_Y, -90, 0b100, cidx(up), 3),
        [Steps::ST_D] = LayerStepMeta(Steps::ST_D, Axis::A_Y, 90, 0b001, cidx(down), 1),

        [Steps::ST_F] = LayerStepMeta(Steps::ST_F, Axis::A_Z, -90, 0b100, cidx(front), 1),
        [Steps::ST_B] = LayerStepMeta(Steps::ST_B, Axis::A_Z, 90, 0b001, cidx(back), 3),

        [Steps::ST_Lb] = LayerStepMeta(Steps::ST_Lb, Axis::A_X, -90, 0b001, cidx(left), 1),
        [Steps::ST_Rb] = LayerStepMeta(Steps::ST_Rb, Axis::A_X, 90, 0b100, cidx(right), 3),

        [Steps::ST_Ub] = LayerStepMeta(Steps::ST_Ub, Axis::A_Y, 90, 0b100, cidx(up), 1),
        [Steps::ST_Db] = LayerStepMeta(Steps::ST_Db, Axis::A_Y, -90, 0b001, cidx(down), 3),

        [Steps::ST_Fb] = LayerStepMeta(Steps::ST_Fb, Axis::A_Z, 90, 0b100, cidx(front), 3),
        [Steps::ST_Bb] = LayerStepMeta(Steps::ST_Bb, Axis::A_Z, -90, 0b001, cidx(back), 1),

        [Steps::ST_l] = LayerStepMeta(Steps::ST_L, Axis::A_X, 90, 0b011, cidx(left), 3),
        [Steps::ST_r] = LayerStepMeta(Steps::ST_R, Axis::A_X, -90, 0b110, cidx(right), 1),

        [Steps::ST_u] = LayerStepMeta(Steps::ST_U, Axis::A_Y, -90, 0b110, cidx(up), 3),
        [Steps::ST_d] = LayerStepMeta(Steps::ST_D, Axis::A_Y, 90, 0b011, cidx(down), 1),

        [Steps::ST_f] = LayerStepMeta(Steps::ST_F, Axis::A_Z, -90, 0b110, cidx(front), 1),
        [Steps::ST_b] = LayerStepMeta(Steps::ST_B, Axis::A_Z, 90, 0b011, cidx(back), 3),

        [Steps::ST_lb] = LayerStepMeta(Steps::ST_Lb, Axis::A_X, -90, 0b011, cidx(left), 1),
        [Steps::ST_rb] = LayerStepMeta(Steps::ST_Rb, Axis::A_X, 90, 0b110, cidx(right), 3),

        [Steps::ST_ub] = LayerStepMeta(Steps::ST_Ub, Axis::A_Y, 90, 0b110, cidx(up), 1),
        [Steps::ST_db] = LayerStepMeta(Steps::ST_Db, Axis::A_Y, -90, 0b011, cidx(down), 3),

        [Steps::ST_fb] = LayerStepMeta(Steps::ST_Fb, Axis::A_Z, 90, 0b110, cidx(front), 3),
        [Steps::ST_bb] = LayerStepMeta(Steps::ST_Bb, Axis::A_Z, -90, 0b011, cidx(back), 1)
};

const unsigned char transform[4][3][2] = {
        {{2, 0}, {2, 1}, {2, 2}},
        {{2, 2}, {1, 2}, {0, 2}},
        {{0, 2}, {0, 1}, {0, 0}},
        {{0, 0}, {1, 0}, {2, 0}},
};


const unsigned char x_color_index_move[4] = {cidx(up), cidx(front), cidx(down), cidx(back)};
const unsigned char y_color_index_move[4] = {cidx(right), cidx(front), cidx(left), cidx(back)};
const unsigned char z_color_index_move[4] = {cidx(right), cidx(up), cidx(left), cidx(down)};


class Cube {
private:
    Steps currentStep;
public:
    Item * items[3][3][3]{};

    Cube() {
        ColorMap map{};
        for (char z = 0; z < 3; z++) {
            map.set.front = ColorIndex::black;
            map.set.back = ColorIndex::black;
            if (z==0)
                map.set.back = ColorIndex::orange;
            else if (z==2)
                map.set.front = ColorIndex::red;
            for (char y = 0; y < 3; y++) {
                map.set.up = ColorIndex::black;
                map.set.down = ColorIndex::black;
                if (y==0)
                    map.set.down= ColorIndex::white;
                else if (y==2)
                    map.set.up = ColorIndex::yellow;
                for (char x = 0; x < 3; x++) {
                    map.set.left = ColorIndex::black;
                    map.set.right = ColorIndex::black;
                    if (x==0)
                        map.set.left = ColorIndex::blue;
                    else if (x==2)
                        map.set.right= ColorIndex::green;
                    this->items[x][y][z] = new Item(map);
                }
            }
        }
    }
/*
    ColorIndex up;      //0
    ColorIndex down;    //1
    ColorIndex left;    //2
    ColorIndex right;   //3
    ColorIndex front;   //4
    ColorIndex back;    //5
*/


    Item * find_item(Axis a, int layer, int step, int index) {
        int x, y, z;
        switch (a) {
            case A_X:
                x = layer;
                y = transform[step][index][0];
                z = transform[step][index][1];
                break;
            case A_Y:
                x = transform[step][index][0];
                y = layer;
                z = transform[step][index][1];
                break;
            case A_Z:
                x = transform[step][index][0];
                y = transform[step][index][1];
                z = layer;
                break;
        }
        return this->items[x][y][z];
    }

    void rotate(Axis axis, int layer_mask, int cnt, int layer_color) {
        for (int layer=0;layer<3;layer++) {
            if (layer_mask & (1<<layer)) {
                const unsigned char *color_index;
                switch (axis) {
                    case A_X:
                        color_index = x_color_index_move;
                        break;
                    case A_Y:
                        color_index = y_color_index_move;
                        break;
                    case A_Z:
                        color_index = z_color_index_move;
                        break;
                }
                for (int c = 0; c < cnt; c++) {
                    ColorMap set[3];
                    for (int i = 0; i < 3; i++)
                        set[i] = find_item(axis, layer, 0, i)->colors;

                    for (int s = 1; s < 4; s++) {
                        for (int i = 0; i < 3; i++) {
                            Item *to = find_item(axis, layer, s - 1, i);
                            Item *from = find_item(axis, layer, s, i);
                            to->colors.items[color_index[s - 1]] = from->colors.items[color_index[s]];
                            if (i != 2) {
                                to->colors.items[layer_color] = from->colors.items[layer_color];
                            }
                        }
                    }
                    for (int i = 0; i < 3; i++) {
                        Item *to = find_item(axis, layer, 3, i);
                        to->colors.items[color_index[3]] = set[i].items[color_index[0]];
                        if (i != 2) {
                            to->colors.items[layer_color] = set[i].items[layer_color];
                        }
                    }
                }
            }
        }
    }



    void execStep(Steps step) {
        if ((step!=Steps::ST_NONE) && (step!=Steps::ST_END)) {
            LayerStepMeta * meta = &step_meta[step];
            rotate(meta->axis, meta->layers, meta->count, meta->color_index);
        }
    }
};


class Controller {
private:
    Cube * cube;
public:
    Steps getNextStep() {
        return Steps::ST_NONE;

        //return (Steps)(random() % (int)Steps::ST_END);
/*
        Steps _steps[] = {Steps::ST_U, Steps::ST_R};
        static int s = 0;
        s++;
        if (s>2)
            s = 0;
        return _steps[s];
*/
    }

    Controller(Cube * cube) {
        this->cube = cube;
    }
};
